package server

import (
	"errors"
	"github.com/rfyiamcool/go-timewheel"
	"io"
	"log"
	"sync"
	"time"
)

const (
	RABBITMQ = "rabbitmq"
	RESTFUL  = "rest"
	GRPC     = "grpc"
)

type Platform struct {
	running      bool
	Name         string
	driver       Driver
	driverRunner func(platform *Platform)
	options      map[string]interface{}
	tw           *timewheel.TimeWheel
	mutex        sync.Mutex
}

func NewPlatform(name string, driver string, optionCalls ...OptionCall) (*Platform, error) {
	tw, err := timewheel.NewTimeWheel(time.Second, 360)
	if err != nil {
		return nil, err
	}
	p := &Platform{Name: name, tw: tw, options: make(map[string]interface{})}
	for _, c := range optionCalls {
		c(p)
	}

	d, err := NewDriver(driver, p.GetOption(driver))
	if err != nil {
		return nil, err
	}
	p.driver = d
	return p, nil
}

func (p *Platform) AddTask(task *Task) error {
	if !p.running {
		log.Printf("add task %s to platform %s error: platform is not running", task.Name, p.Name)
		return errors.New("platform is not running")
	}
	log.Printf("add task %s to platform %s", task.Name, p.Name)
	if task.Delay > 0 {
		p.tw.Add(task.Delay, p.Callback(task))
	} else {
		p.Callback(task)()
	}
	return nil
}

func (p *Platform) Callback(task *Task) func() {
	return func() {
		p.driver.Callback(task)
	}
}

func (p *Platform) Run() {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	if p.running {
		return
	}
	p.tw.Start()
	err := p.driver.Start(p)
	if err != nil {
		log.Printf("platform %s start error: %s", p.Name, err)
		return
	}
	p.running = true
	log.Printf("platform %s running", p.Name)
}

func (p *Platform) Stop() {
	p.tw.Stop()
	p.running = false
	log.Printf("platform %s stopped", p.Name)
}

func (p *Platform) GetOption(key string) interface{} {
	return p.options[key]
}

func (p *Platform) SetOption(key string, option interface{}) {
	p.options[key] = option
}

type Task struct {
	Name    string
	Payload []byte
	Delay   time.Duration
	offset  int
}

func (t *Task) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	n = copy(p, t.Payload[t.offset:])
	if t.offset >= len(t.Payload) {
		t.offset = 0
		return n, io.EOF
	}
	t.offset += n
	return n, nil
}

func NewTask(name string, payload []byte, delay time.Duration) *Task {
	return &Task{
		Name:    name,
		Payload: payload,
		Delay:   delay,
	}
}

type Driver interface {
	Callback(task *Task)
	Start(platform *Platform) error
	Stop()
}

func NewDriver(driver string, option interface{}) (Driver, error) {
	switch driver {
	case GRPC:
		return nil, nil
	case RESTFUL:
		option, _ := option.(RestfulOption)
		return NewRestful(option)
	case RABBITMQ:
		option, _ := option.(RabbitmqOption)
		return NewRabbitmq(option)
	default:
		return nil, errors.New("undefined driver")
	}
}

type OptionCall func(platform *Platform)
